home *** CD-ROM | disk | FTP | other *** search
/ MacFormat 1997 October / macformat-055.iso / mac / Shareware Plus / Developers / VideoToolbox / VideoToolboxSources / Rush.c < prev    next >
Encoding:
C/C++ Source or Header  |  1997-07-02  |  10.5 KB  |  315 lines  |  [TEXT/CWIE]

  1. /* Rush.c
  2.  
  3. COPYRIGHT:
  4. Copyright Denis Pelli, 1997. This file may be distributed freely as long
  5. as this notice accompanies it and any changes are noted in the source. 
  6. It is distributed as is, without any warranty implied or provided.  We
  7. accept no liability for any damage or loss resulting from the use of
  8. this software.
  9.  
  10. PURPOSE:
  11. Run any critical user function with minimal interruption by Mac OS
  12. system software. Without Rush, the Mac OS and device drivers steal
  13. chunks of time whenever they like. E.g. the Zip disk driver, when no
  14. disk is inserted, steals 2 ms once every 3 seconds. For a loop showing a
  15. real-time movie, the possibility of losing 2 milliseconds, out of a
  16. frame time of say 13 ms, substantially reduces the maximum number of
  17. pixels that can be shown per frame if we insist on never missing a
  18. frame. Rush implements two alternate solutions. Apple's guidelines
  19. suggest that driver interrupt tasks should be very brief, passing any
  20. lengthy tasks to the Deferred Task Manager to run later. When
  21. priorityLevel is -1, Rush runs the user code as a deferred task, which
  22. runs normally, except that all other deferred tasks are blocked until
  23. our code finishes. When priorityLevel is 0 the user code runs normally.
  24. When priorityLevel is >0 the user code runs at that raised processor
  25. priority. The Deferred Task Manager doesn't run at all until the
  26. processor priority goes back to zero. Thus any nonzero priorityLevel
  27. will block all deferred tasks. A positive priorityLevel will also block
  28. some primary interrupt tasks; more are blocked the higher the
  29. priorityLevel. At priorityLevel 7 nearly all interrupts are blocked.
  30.  
  31. When priorityLevel is nonzero we block all the Mac's deferred tasks
  32. while the user function is run. When priorityLevel is 1 (or more) we
  33. block deferred tasks by raising the priority before calling the user
  34. function. When priorityLevel is -1 we block deferred tasks by calling
  35. the user function from within a deferred task.
  36.  
  37. The deferred-task solution is attractive. All deferred tasks are blocked
  38. until ours finishes, and all urgent interrupt processes (including VBL
  39. and Time Manager updates) occur normally. And keyboard and mouse still
  40. work. This elegant solution was suggested by Bo3b Johnson at Apple
  41. Developer Support on 4/19/97 (Follow-up:  418098). A limitation is
  42. that we can't use the 68k fpu in the deferred task, because we
  43. don't save & restore the fpu registers. (Apple advises against it.)
  44.  
  45. The raised-priority solution has a drawback. Raising priority (to 1 or
  46. more) does block the deferred tasks, but also results in abnormal
  47. operation of the Time Manager (overflow and coarse steps) and freezes
  48. the mouse and keyboard.
  49.  
  50. 4/22/97 Testing with MATLAB LoopTest.m indicates that Rush.mex runs fine
  51. at all priorities. However, a 2 ms interruption due to the Zip driver
  52. (when no disk is inserted) occurs about one every three seconds at
  53. priorities -1, 0, and 1, and only disappears at priorities 2 and higher.
  54. Contrary to Apple's rules, the Zip driver's 2 ms interruption is NOT a
  55. deferred task. We're hoping that Iomega will fix their driver soon. For
  56. news you might look here:
  57. <http://www.macintouch.com/jazprobs.html>
  58.  
  59. RESTRICTIONS:
  60. When the code is compiled to use the 68881 floating point unit (or the
  61. equivalent fpu built into the high-end 680x0 processor) and the
  62. priorityLevel is -1, the user function supplied to Rush should not use
  63. floating point. This is because the user function will run at interrupt
  64. time, and our code below doesn't save or restore the floating point
  65. registers. Apple suggests this approach, banning use of the floating
  66. point unit at interrupt time, because saving and restoring the 68k
  67. floating point registers is slow. Actually, in this particular
  68. application that delay would be acceptable, because it wouldn't affect
  69. the running speed, only the set up time, but I don't in fact know how to
  70. save and restore the registers. Apple's information on this presumed
  71. fluency with 68K assembly, which I lack.
  72.  
  73. EXAMPLE 1:
  74. Let's rush the calculation of c=a+b. (This simple example is solely for
  75. the sake of exposition. I can't think of any reason to rush this. In
  76. real life you'll only want to rush code that must synchronize with
  77. external events, e.g. displaying or recording.)
  78.  
  79. typedef struct {
  80.      int a,b,c;
  81. } Abc;
  82.  
  83. void main(void)
  84. {
  85.      Abc abc;
  86.      int priorityLevel=7;
  87.  
  88.      abc.a=a;
  89.      abc.b=b;
  90.      Rush(priorityLevel,&Summer,&abc);
  91.      c=abc.c;
  92. }
  93.  
  94. void Summer(void *argPtr)
  95. {
  96.      Abc *abcPtr;
  97.  
  98.      abcPtr=argPtr;
  99.      abcPtr->c = abcPtr->a + abcPtr->b;
  100. }
  101.  
  102. EXAMPLE 2:
  103. This example illustrates something closer to the use for which Rush.c
  104. was created. Here we call back to the main application (i.e. MATLAB) to
  105. rush high-level code. The C subroutine called "mexFunction" is a MEX
  106. function, callable from MATLAB programs, e.g. Rush('c=a+b'). The
  107. mexFunction receives a string of MATLAB code, e.g. "c=a+b". Our rushed C
  108. routine, fun(), asks the MATLAB application to use "eval" to execute the
  109. string, which has the effect of rushing this bit of MATLAB code. Again,
  110. in real life you'll only want to rush code that must synchronize with
  111. external events. We now Rush all our MATLAB display loops.
  112.  
  113. typedef struct{
  114.     mxArray *mxString;
  115.     long error;
  116. } Stuff;
  117.  
  118. void mexFunction(int nlhs, mxArray *plhs[], int nrhs, CONSTmxArray *prhs[])
  119. {
  120.     Stuff stuff;
  121.     
  122.     stuff.mxString=prhs[0];
  123.     stuff.error=0;
  124.     Rush(priorityLevel,&fun,&stuff);
  125. }
  126.  
  127. void fun(void *argPtr)
  128. {
  129.     Stuff *stuffPtr;
  130.     mxArray *plhs[1],*prhs[1];
  131.     
  132.     stuffPtr=(Stuff *)argPtr;
  133.     prhs[0]=stuffPtr->mxString;
  134.     mexSetTrapFlag(1);
  135.     stuffPtr->error=mexCallMATLAB(0,plhs,1,prhs,"eval");
  136.     mexSetTrapFlag(0);
  137. }
  138.  
  139. HISTORY:
  140. 4/21/97        dgp        Wrote it as a MEX file.
  141. 4/22/97        dgp        Polished the code. Marked the done flag as "volatile", 
  142.                     since it's set by the deferred task at interrupt time.
  143. 5/2/97        dgp     Fixed bug in definition of GetA1 declaration to fix 68K crash reported by dhb.
  144. 6/22/97        dgp        Updated comments above.
  145. 7/1/97        dgp        As requested by Josh Solomon, removed the mex-specific stuff to leave this
  146.                     generic version that i'm adding to the VideoToolbox. Josh wants to use
  147.                     it to create a RUSH facility in Mathematica, like the one I created in MATLAB.
  148. KNOWN BUGS:
  149. */
  150. #include <VideoToolbox.h>
  151. #ifndef __PROCESSES__
  152.     #include <Processes.h>    // ProcessSerialNumber
  153. #endif
  154. #ifndef __TRAPS__
  155.     #include <Traps.h>    // _DTInstall
  156. #endif
  157. #define CODE_RESOURCE MATLAB
  158. #if GENERATING68K
  159.     #pragma parameter __D0 GetA1()
  160.     long GetA1(void)= 0x2009;    // MOVE.L A1,D0
  161.     #pragma parameter __D0 GetA4()
  162.     long GetA4(void)= 0x200C;    // MOVE.L A4,D0
  163.     #pragma parameter __D0 GetA5()
  164.     long GetA5(void)= 0x200D;    // MOVE.L A5,D0
  165.     #if THINK_C
  166.         #pragma parameter __D0 SetA4(__D0)
  167.         long SetA4(long) = 0xC18C;    // EXG D0,A4
  168.     #else
  169.         long SetA4(long:__D0):__D0 = 0xC18C;    // EXG D0,A4
  170.     #endif
  171. #else
  172.     #define GetA1()        0L
  173.     #define GetA4()        0L
  174.     #define GetA5()        0L
  175.     #define SetA4(x)    0L
  176. #endif
  177. typedef struct{
  178.     ProcessSerialNumber psn;
  179.     DeferredTask *deferredTaskPtr;
  180.     volatile long A,error,failedAttempts;
  181.     void (*functionPtr)(void *argPtr);
  182.     void *argPtr;
  183.     volatile Boolean done;
  184. } Stuff;
  185. #if GENERATINGPOWERPC
  186.     static void OurDeferredTask(register Stuff *stuffPtr);
  187. #else
  188.     static void OurDeferredTask(void);
  189. #endif
  190. static void CallFunction(Stuff *stuffPtr);
  191.  
  192. int Rush(int priorityLevel,void (*functionPtr)(void *argPtr),void *argPtr);
  193.  
  194. int Rush(int priorityLevel,void (*functionPtr)(void *argPtr),void *argPtr)
  195. {
  196.     static Boolean firstTime=1,deferAvailable;
  197.     static DeferredTaskUPP deferredTaskUPP;
  198.     int oldPriority;
  199.     DeferredTask deferredTask;
  200.     int error;
  201.     Stuff stuff;
  202.     
  203.     if(firstTime){
  204.         deferAvailable=TrapAvailable(_DTInstall);
  205.         deferredTaskUPP=NewDeferredTaskProc(OurDeferredTask);
  206.         firstTime=0;
  207.     }
  208.     if(priorityLevel<-1 || priorityLevel>7)PrintfExit("%s: Illegal priorityLevel %d.",__FILE__,priorityLevel);
  209.     error=GetCurrentProcess(&stuff.psn);
  210.     stuff.functionPtr=functionPtr;
  211.     stuff.argPtr=argPtr;
  212.     stuff.done=0;
  213.     stuff.error=0;
  214.     stuff.failedAttempts=0;
  215.     stuff.deferredTaskPtr=NULL;
  216.     switch(priorityLevel){
  217.     case -1:
  218.         // Run user's function as a deferred task.
  219.         if(!deferAvailable)PrintfExit("%s: Your System is too old: no Deferred Task Manager.",__FILE__);
  220.         if(GetPriority()>0){
  221.             SetPriority(0);
  222.             PrintfExit("%s: Can't defer task while processor priority is above zero.",__FILE__);
  223.         }
  224.         #if CODE_RESOURCE
  225.             stuff.A=GetA4();
  226.         #else
  227.             stuff.A=GetA5();
  228.         #endif
  229.         stuff.deferredTaskPtr=&deferredTask;
  230.         deferredTask.qType=dtQType;
  231.         deferredTask.dtAddr=deferredTaskUPP;
  232.         deferredTask.dtReserved=0;
  233.         deferredTask.dtParam=(long)&stuff;
  234.         error=DTInstall(stuff.deferredTaskPtr);
  235.         if(error)PrintfExit("%s: DTInstall error %d.",__FILE__,error);
  236.         while(!stuff.done) ;    // wait until our deferredTask is done
  237.         break;
  238.     case 0:
  239.         // Run user's function normally. 
  240.         CallFunction(&stuff);
  241.         break;
  242.     default:
  243.         // Run user's function at raised processor priority.
  244.         oldPriority=GetPriority();
  245.         SetPriority(priorityLevel);
  246.         CallFunction(&stuff);
  247.         SetPriority(oldPriority);
  248.         break;
  249.     }
  250.     if(stuff.error==1234)
  251.         PrintfExit("%s: user function never ran; always interrupted wrong process.",__FILE__);
  252.     if(stuff.failedAttempts)
  253.         printf("%s: WARNING: user function ran only after %ld attempts that interrupted other processes.\n",__FILE__,stuff.failedAttempts);
  254.     return stuff.failedAttempts;
  255. }
  256.  
  257. #if (THINK_C || THINK_CPLUS || SYMANTEC_C)
  258.     #pragma options(!profile)    // it would be dangerous to call the profiler from here
  259.     #pragma options(assign_registers,redundant_loads)
  260. #endif
  261. #if __MWERKS__ && __profile__
  262.     #pragma profile off            // on 68k it would be dangerous to call the profiler from here
  263. #endif
  264.  
  265. #if GENERATINGPOWERPC
  266.     static void OurDeferredTask(register Stuff *stuffPtr)
  267.     {
  268.         CallFunction(stuffPtr);
  269.     }
  270. #else
  271.     static void OurDeferredTask(void)
  272.     {
  273.         Stuff *stuffPtr;
  274.         long oldA;
  275.  
  276.         stuffPtr=(void *)GetA1();
  277.         #if CODE_RESOURCE
  278.             oldA=SetA4(stuffPtr->A);
  279.         #else
  280.             oldA=SetA5(stuffPtr->A);
  281.         #endif
  282.         CallFunction(stuffPtr);
  283.         #if CODE_RESOURCE
  284.             SetA4(oldA);
  285.         #else
  286.             SetA5(oldA);
  287.         #endif
  288.     }
  289. #endif
  290.  
  291. static void CallFunction(Stuff *stuffPtr)
  292. {
  293.     ProcessSerialNumber psn;
  294.     Boolean isOurs;
  295.     int error;
  296.  
  297.     error=GetCurrentProcess(&psn);
  298.     error=SameProcess(&stuffPtr->psn,&psn,&isOurs);    // compare psns.
  299.     if(isOurs){
  300.         // It's safe. Let's do it.
  301.         (*stuffPtr->functionPtr)(stuffPtr->argPtr);
  302.         stuffPtr->error=0;
  303.         stuffPtr->done=1;
  304.     }else{
  305.         // Oops. We're interrupting some other process. It's unsafe to run now.
  306.         // Let's lie low, and reinstall ourselves to try again later.
  307.         stuffPtr->failedAttempts++;
  308.         if(stuffPtr->failedAttempts<10 && stuffPtr->deferredTaskPtr!=NULL)
  309.             stuffPtr->error=DTInstall(stuffPtr->deferredTaskPtr);
  310.         else stuffPtr->error=1234;
  311.         if(stuffPtr->error)stuffPtr->done=1;    // Can't do it; give up.
  312.     }
  313. }
  314.  
  315.